package cn.wizzer.app.web.commons.ext.websocket.main.base;

import cn.wizzer.app.crown.modules.models.constant.Fields;
import cn.wizzer.app.netty.modules.models.NettyConstant;
import cn.wizzer.app.user.modules.models.constant.ChatConstant;
import cn.wizzer.app.web.commons.ext.websocket.main.action.BaseNettyAction;
import cn.wizzer.app.web.commons.ext.websocket.main.action.NettyAction;
import cn.wizzer.app.web.commons.ext.websocket.main.exception.NettyException;
import cn.wizzer.app.web.commons.ext.websocket.main.filter.BaseNettyFilter;
import cn.wizzer.app.web.commons.ext.websocket.main.monitor.BaseNettyMonitor;
import cn.wizzer.framework.util.Result;
import cn.wizzer.framework.base.exceptions.BizException;
import cn.wizzer.framework.util.JSONUtil;
import cn.wizzer.framework.util.param.Param4MapVerify;
import cn.wizzer.framework.util.param.ParamValidation;
import cn.wizzer.framework.util.param.ParamVerifyException;
import com.alibaba.fastjson.JSONObject;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.group.ChannelGroup;
import io.netty.channel.group.DefaultChannelGroup;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import io.netty.util.concurrent.GlobalEventExecutor;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.log.Log;
import org.nutz.log.Logs;

import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import static cn.wizzer.app.user.modules.models.constant.ChatConstant.DEFAULT;

@IocBean(name = "textWebSocketFrameHandler")
@ChannelHandler.Sharable
public class TextWebSocketFrameHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {
    private static final Log log = Logs.get();
    //所有聊天室

    public static final Map<String, ChannelGroup> channelGroupMap = new HashMap<>();


    static {
        //启动时创建默认房
        channelGroupMap.put(ChatConstant.DEFAULT, new DefaultChannelGroup(GlobalEventExecutor.INSTANCE));
    }

    public static DefaultChannelGroup createChannelGroup(String roomId) {
        DefaultChannelGroup channelGroup = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
        channelGroupMap.put(roomId, channelGroup);
        return channelGroup;
    }

    /**
     * 通过idStr获取指定channel
     *
     * @param toChannel
     * @return
     */
    public static Channel findChannel(String toChannel) {
        ChannelGroup channels = channelGroupMap.get(DEFAULT);
        for (Channel channel : channels) {
            if (channel.id().toString().equals(toChannel)) {
                return channel;
            }
        }
        return null;
    }


    /**
     * 消息通知-接收
     *
     * @param ctx
     * @param msg
     * @throws Exception
     */
    @Override
    protected void channelRead0(ChannelHandlerContext ctx,
                                TextWebSocketFrame msg) throws Exception {
        Channel income = ctx.channel();
        try {
            //路由
            String uri = msg.text();
            NettyRouter router = new NettyRouter(uri, income);
            //业务逻辑实现类
            BaseNettyAction action = BaseNettyAction.instance(router.getUri());
            //接口注解
            NettyAction nettyAction = action.getClass().getAnnotation(NettyAction.class);
            //拦截器-接口指定
            Class<? extends BaseNettyFilter>[] fClasses = nettyAction.filters();
            //region 拦截器校验
            if (fClasses.length > 0) {
                for (Class<? extends BaseNettyFilter> filterClass : fClasses) {
                    Result result = BaseNettyFilter.instance(filterClass).doFilter(router, action);
                    if (result != null) {
                        send(income, result.addKey(router.getParams().getString(Fields.CALL_BACK_PARAM)));
                        return;
                    }
                }
            }
            //endregion
            //region Subscribe
            if (null != router.getParam(NettyConstant.SUBSCRIBE)) {
                String subscribeParam = router.getParam(NettyConstant.SUBSCRIBE).toString();
                //取消订阅
                if (Fields.TRUE_STR.equals(router.getParam(NettyConstant.CANCEL_SUBSCRIBE))) {
                    removeSubscribe(income, router, subscribeParam);
                } else {
                    Result result = action.subscribe(subscribeParam);
                    //添加订阅
                    if (result.getCode() == 0) {
                        addSubscribe(income, router, subscribeParam);
                    }
                    send(income, result);
                }
            }
            //endregion
            //region Real Time
            else {
                //参数验证
                Method actionMethod = action.getClass().getMethod("action", Channel.class, JSONObject.class);
                Parameter parameter = actionMethod.getParameters()[1];
                Param4MapVerify param4MapVerify = parameter.getAnnotation(Param4MapVerify.class);
                if (param4MapVerify != null) {
                    ParamValidation paramValidation = new ParamValidation();
                    paramValidation.validation(Result.createHashMap(parameter, router.getParams()));
                }
                //action logic
                Result result = action.action(income, router.getParams());
                //伪响应
                send(income, result.addKey(router.getParams().getString(Fields.CALL_BACK_PARAM)));
            }
            //endregion
        } catch (BizException be) {
            send(income, Result.error().setMsg(be.getRemand()));
        } catch (ParamVerifyException pe) {
            log.error(pe);
            send(income, Result.error());
        } catch (RuntimeException re) {
            //异常处理
            send(income, NettyException.doException(re));
            re.printStackTrace();
        } catch (Exception e) {
            log.error(e);
        }
    }

    /**
     * 添加订阅
     *
     * @param income
     * @param router
     * @param subscribeParam
     */
    private void addSubscribe(Channel income, NettyRouter router, String subscribeParam) {
        String key = router.getUri() + ":" + subscribeParam;
        ChannelGroup channels = channelGroupMap.get(key);
        if (channels == null) {
            channels = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
        }
        channels.add(income);
        channelGroupMap.put(key, channels);
    }

    /**
     * 取消订阅
     *
     * @param income
     * @param router
     * @param subscribeParam
     */
    private void removeSubscribe(Channel income, NettyRouter router, String subscribeParam) {
        String key = router.getUri() + ":" + subscribeParam;
        ChannelGroup channels = channelGroupMap.get(key);
        if (channels == null) {
            return;
        }
        channels.remove(income);
        //销毁
        if (channels.size() == 0) {
            channels.close();
            channelGroupMap.remove(key);
        }
    }

    /**
     * 发送消息
     *
     * @param channel
     * @param data
     */
    public static void send(Channel channel, Object data) {
        if (data instanceof String) {
            channel.writeAndFlush(new TextWebSocketFrame(data.toString()));
        } else {
            channel.writeAndFlush(new TextWebSocketFrame(JSONUtil.toJSONString(data)));
        }
    }

    /**
     * 订阅通知
     *
     * @param groupKey
     * @param data
     */
    public static void send(String groupKey, Object data) {
        ChannelGroup channels = channelGroupMap.get(groupKey);
        if (channels != null) {
            Map result = Result.createMap(Fields.DATA, data, Fields.KEY, groupKey);
            channels.writeAndFlush(new TextWebSocketFrame(JSONUtil.toJSONString(result)));
        }
    }

    /**
     * 建立连接
     *
     * @param ctx
     * @throws Exception
     */
    @Override
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
        BaseNettyMonitor.instances().forEach(monitor -> monitor.added(ctx));
    }

    /**
     * 通道关闭 保活处理？
     *
     * @param ctx
     * @throws Exception
     */
    @Override
    public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
        // 本地缓存有没有青 nettycahe localmap
        for (Iterator<BaseNettyMonitor> iterator = BaseNettyMonitor.instances().iterator(); iterator.hasNext(); ) {
            BaseNettyMonitor monitor = iterator.next();
            monitor.removed(ctx);
        }
    }

}
